using System;
using System.Collections.Generic;
using System.Text;
using System.Web;

namespace ca.metaobjects.csogateway.Reflection
{
	[Serializable]
	internal class DependencyGraph
	{
		#region Static Property - Dependency graph for current HttpRequest
		internal static DependencyGraph ForCurrentHttpRequest
		{
			get
			{
				if (HttpContext.Current != null)
				{
					if (HttpContext.Current.Items["ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph"] == null)
						HttpContext.Current.Items["ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph"] = new DependencyGraph();

					return (DependencyGraph)HttpContext.Current.Items["ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph"];
				}
				else //For testing without an HttpContext
				{
					if (AppDomain.CurrentDomain.GetData("ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph") == null)
						AppDomain.CurrentDomain.SetData("ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph", new DependencyGraph());

					return (DependencyGraph)AppDomain.CurrentDomain.GetData("ca.metaobjects.csogateway.Controls.Importer.ScriptImportGraph");
				}


			}
		}
		#endregion

		#region Static methods for descritor strings

		internal static string DescriptorForCsoType(CsoType aCsoType)
		{
			if (aCsoType is CsoNativeType)
			{
				return ((CsoNativeType)aCsoType).ScriptFileInfo.FileName;
			}
			else // aCsoType is CsoGeneratedType
			{
				return String.Format("(({0})({1}))", ((CsoGeneratedType)aCsoType).FullTypeName, ((CsoGeneratedType)aCsoType).ServerSideType.AssemblyQualifiedName);
			}
		}

		internal static string DescriptorForLibraryScriptFileInformation(LibraryScriptFileInformation fileInfo)
		{
			return fileInfo.FileName;
		}

		internal static string DescriptorForExtensionFile(Uri fileUrl)
		{
			return fileUrl.OriginalString;
		}

		internal static string CsoGeneratedTypeNameInDescriptor(string descriptor)
		{
			return descriptor.Split(new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries)[0];
		}

		internal static string AssemblyQualifiedNameInCsoGeneraedTypeDescriptor(string descriptor)
		{
			return descriptor.Split(new char[] { '(', ')' }, StringSplitOptions.RemoveEmptyEntries)[1];
		}

		#endregion

		#region Private fields

		private List<ScriptDescriptor> descriptors;
		private List<ScriptDependency> dependencies;

		#endregion

		#region Constructors

		private DependencyGraph()
		{
			descriptors = new List<ScriptDescriptor>(32);
			dependencies = new List<ScriptDependency>(64);
		}

		#endregion

		#region Search graph
		internal bool Contains(CsoType aCsoType)
		{
			return containsCsoType(aCsoType);
		}

		internal bool Contains(LibraryScriptFileInformation aScriptFileInfo)
		{
			return containsScriptFileInformation(aScriptFileInfo);
		}

		internal bool Contains(Uri anExtensionFileUrl)
		{
			return containsExtensionFile(anExtensionFileUrl);
		}

		private bool containsCsoType(CsoType csoType)
		{
			return findScriptDescriptor(DescriptorForCsoType(csoType)) != null;
		}

		private bool containsScriptFileInformation(LibraryScriptFileInformation fileInfo)
		{
			return findScriptDescriptor(DescriptorForLibraryScriptFileInformation(fileInfo)) != null;
		}

		private bool containsExtensionFile(Uri anExtensionFileUrl)
		{
			return findScriptDescriptor(DescriptorForExtensionFile(anExtensionFileUrl)) != null;
		}

		private ScriptDescriptor findScriptDescriptor(string scriptDescriptorString)
		{
			ScriptDescriptor foundValue = null;
			for (int i = 0; i < descriptors.Count; ++i)
			{
				if (descriptors[i].descriptor == scriptDescriptorString)
				{
					foundValue = descriptors[i];
					break;
				}
			}

			return foundValue;
		}
		#endregion

		#region Add script element to graph
		private void addCsoGeneratedType(CsoGeneratedType aGenType)
		{
			addScriptDescriptor(DescriptorForCsoType(aGenType), DescriptorType.CsoGeneratedTypeName);
		}

		private void addCsoGeneratedTypeExtensionFile(Uri extensionFileUrl)
		{
			addScriptDescriptor(DescriptorForExtensionFile(extensionFileUrl), DescriptorType.ExtensionFile);
		}

		private void addCsoNativeTypeScriptFile(LibraryScriptFileInformation fileInfo)
		{
			addScriptDescriptor(DescriptorForLibraryScriptFileInformation(fileInfo), DescriptorType.CsoNativeScriptFile);
		}
		#endregion

		#region Add dependency between script element
		private void addCsoGeneratedTypeToCsoGeneratedTypeDependency(CsoGeneratedType dependentCsoGenType, CsoGeneratedType providerCsoGenType)
		{
			addScriptDependency(DescriptorForCsoType(dependentCsoGenType), DescriptorForCsoType(providerCsoGenType));
		}

		private void addCsoGeneratedTypeToScriptFileDependency(CsoGeneratedType aGenType, LibraryScriptFileInformation fileInfo)
		{
			addScriptDependency(DescriptorForCsoType(aGenType), DescriptorForLibraryScriptFileInformation(fileInfo));
		}

		private void addScriptFileToScriptFileDependency(LibraryScriptFileInformation dependentFileInfo, LibraryScriptFileInformation providerFileInfo)
		{
			addScriptDependency(DescriptorForLibraryScriptFileInformation(dependentFileInfo),
				DescriptorForLibraryScriptFileInformation(providerFileInfo));
		}

		private void addExtensionFileDependency(CsoGeneratedType aGenType)
		{
			addScriptDependency(DescriptorForExtensionFile(aGenType.ExtensionUrl), DescriptorForCsoType(aGenType));
		}

		private void addScriptDescriptor(string scriptDescriptorString, DescriptorType sType)
		{
			if (findScriptDescriptor(scriptDescriptorString) == null)
			{
				ScriptDescriptor s = new ScriptDescriptor();
				s.descriptor = scriptDescriptorString;
				s.descriptorType = sType;
				descriptors.Add(s);
			}
		}

		private void addScriptDependency(string dependant, string provider)
		{
			bool found = false;
			for (int i = 0; i < dependencies.Count; ++i)
			{
				if ((dependencies[i].start.descriptor == dependant) && (dependencies[i].end.descriptor == provider))
				{
					found = true;
					break;
				}
			}

			if (!found)
			{
				ScriptDependency d = new ScriptDependency();
				d.start = findScriptDescriptor(dependant);
				d.end = findScriptDescriptor(provider);

				if (d.start == null || d.end == null)
					throw new CsoGatewayError(ErrorMessage.I_000);

				dependencies.Add(d);
			}
		}

		#endregion

		#region Graph topological order
		private List<ScriptDependency> findScriptDependencyWithStart(ScriptDescriptor aStart)
		{
			List<ScriptDependency> valuesFound = new List<ScriptDependency>();

			for (int i = 0; i < dependencies.Count; ++i)
			{
				if (dependencies[i].start == aStart)
				{
					valuesFound.Add(dependencies[i]);
				}
			}

			return valuesFound;
		}

		private List<ScriptDependency> findScriptDependencyWithEnd(ScriptDescriptor aEnd)
		{
			List<ScriptDependency> valuesFound = new List<ScriptDependency>();

			for (int i = 0; i < dependencies.Count; ++i)
			{
				if (dependencies[i].end == aEnd)
				{
					valuesFound.Add(dependencies[i]);
				}
			}

			return valuesFound;
		}

		internal List<ScriptDescriptor> generateTopologicalOrder()
		{
			List<ScriptDescriptor> orderedList = new List<ScriptDescriptor>(descriptors.Count);
			
			List<ScriptDescriptor> noIncomingList = new List<ScriptDescriptor>();
			for (int d = 0; d < descriptors.Count; ++d)
			{
				bool foundIncoming = false;
				for (int p = 0; p < dependencies.Count; ++p)
				{
					if (dependencies[p].end == descriptors[d])
					{
						foundIncoming = true;
						break;
					}

				}
				if (!foundIncoming)
					noIncomingList.Add(descriptors[d]);
			}

			while (noIncomingList.Count > 0)
			{
				ScriptDescriptor n = noIncomingList[0];
				noIncomingList.Remove(n);
				orderedList.Add(n);

				ScriptDependency e;
				List<ScriptDependency> nAsStart = findScriptDependencyWithStart(n);
				bool foundIncoming;

				for (int i = 0; i < nAsStart.Count; ++i)
				{
					e = nAsStart[i];
					dependencies.Remove(e);

					foundIncoming = false;
					for (int j = 0; j < dependencies.Count; ++j)
					{
						if (dependencies[j].end == e.end)
						{
							foundIncoming = true;
							break;
						}
					}

					if (!foundIncoming)
					{
						noIncomingList.Add(e.end);
					}
				}
			}

			if (dependencies.Count > 0)
				throw new CsoGatewayException(ExceptionMessage.I_002);

			return orderedList;
		}
		#endregion

		#region Filling with CsoTypes
		internal void fillDependencyGraphForCsoType(CsoType csoType)
		{
			if (csoType is CsoGeneratedType)
			{
				fillDependencyGraphForCsoGeneratedType(csoType as CsoGeneratedType);
			}
			else if (csoType is CsoNativeType)
			{
				fillDependencyGraphForCsoNativeType(csoType as CsoNativeType);
			}
		}

		private void fillDependencyGraphForCsoGeneratedType(CsoGeneratedType csoGeneratedType)
		{
			if (!containsCsoType(csoGeneratedType))
			{
				addCsoGeneratedType(csoGeneratedType);

				if (csoGeneratedType.ExtensionUrl != null)
				{
					addCsoGeneratedTypeExtensionFile(csoGeneratedType.ExtensionUrl);
					addExtensionFileDependency(csoGeneratedType);
				}

				for (int i = 0; i < csoGeneratedType.StrongDependencies.Count; ++i)
				{
					if (csoGeneratedType.StrongDependencies[i] is CsoGeneratedType)
					{
						fillDependencyGraphForCsoGeneratedType(csoGeneratedType.StrongDependencies[i] as CsoGeneratedType);
						addCsoGeneratedTypeToCsoGeneratedTypeDependency(csoGeneratedType, csoGeneratedType.StrongDependencies[i] as CsoGeneratedType);
					}
					else // (csoGeneratedType.StrongDependencies[i] is CsoNativeType)
					{
						fillDependencyGraphForCsoNativeType(csoGeneratedType.StrongDependencies[i] as CsoNativeType);
						addCsoGeneratedTypeToScriptFileDependency(csoGeneratedType, (csoGeneratedType.StrongDependencies[i] as CsoNativeType).ScriptFileInfo);
					}
				}

				for (int i = 0; i < csoGeneratedType.WeakDependencies.Count; ++i)
				{
					if (csoGeneratedType.WeakDependencies[i] is CsoGeneratedType)
					{
						fillDependencyGraphForCsoGeneratedType(csoGeneratedType.WeakDependencies[i] as CsoGeneratedType);
					}
					else // (csoGeneratedType.WeakDependencies[i] is CsoNativeType)
					{
						fillDependencyGraphForCsoNativeType(csoGeneratedType.WeakDependencies[i] as CsoNativeType);
					}
				}
			}
		}

		private void fillDependencyGraphForCsoNativeType(CsoNativeType csoNativeType)
		{
			fillDependencyGraphForScriptFile(csoNativeType.ScriptFileInfo);
		}

		private void fillDependencyGraphForScriptFile(LibraryScriptFileInformation fileInfo)
		{
			if (!containsScriptFileInformation(fileInfo))
			{
				addCsoNativeTypeScriptFile(fileInfo);

				for (int i = 0; i < fileInfo.ContainedTypes.Count; ++i)
				{
					for (int j = 0; j < fileInfo.ContainedTypes[i].StrongDependencies.Count; ++j)
					{
						if (fileInfo != ((CsoNativeType)fileInfo.ContainedTypes[i].StrongDependencies[j]).ScriptFileInfo)
						{
							fillDependencyGraphForCsoNativeType(((CsoNativeType)fileInfo.ContainedTypes[i].StrongDependencies[j]));
							addScriptFileToScriptFileDependency(fileInfo, ((CsoNativeType)fileInfo.ContainedTypes[i].StrongDependencies[j]).ScriptFileInfo);
						}
					}

					for (int j = 0; j < fileInfo.ContainedTypes[i].WeakDependencies.Count; ++j)
					{
						if (fileInfo != ((CsoNativeType)fileInfo.ContainedTypes[i].WeakDependencies[j]).ScriptFileInfo)
						{
							fillDependencyGraphForCsoNativeType(((CsoNativeType)fileInfo.ContainedTypes[i].WeakDependencies[j]));
						}
					}
				}
			}
		}

		#endregion

		#region Merging
		internal void Merge(DependencyGraph graph)
		{
			for (int i = 0; i < graph.descriptors.Count; ++i)
			{
				this.addScriptDescriptor(graph.descriptors[i].descriptor, graph.descriptors[i].descriptorType);
			}
			for (int i = 0; i < graph.dependencies.Count; ++i)
			{
				this.addScriptDependency(graph.dependencies[i].start.descriptor, graph.dependencies[i].end.descriptor);
			}

		}
		#endregion

		#region Class ScriptDescriptor
		[Serializable]
		internal class ScriptDescriptor
		{
			internal DescriptorType descriptorType;
			internal string descriptor;
		}
		#endregion

		#region Class ScriptDependency
		[Serializable]
		internal class ScriptDependency
		{
			internal ScriptDescriptor start;
			internal ScriptDescriptor end;

			internal ScriptDependency()
			{
				start = new ScriptDescriptor();
				end = new ScriptDescriptor();
			}
		}
		#endregion

		#region Enum DescriptorType
		internal enum DescriptorType : byte
		{
			CsoNativeScriptFile = 0,
			CsoGeneratedTypeName = 1,
			ExtensionFile = 2
		}
		#endregion
	}
}
